AWS Amplify Gen2 の defineFunction が Node.js 以外もサポートしました
いわさです。
AWS Amplify Gen2 では defineFunction というコンポーネントを使って Lambda 関数をデプロイすることが出来ます。
しかし、これまでこの機能は Node.js ランタイムのみをサポートしており、Python など他の関数をデプロイしたい場合には CDK 側で頑張ってデプロイする必要がありました。
昨日、以下のプルリクエストが Amplify Backend の main ブランチにマージされ、@aws-ampliufy/backend@1.13.0 から Node.js 以外のランタイムも defineFunction でデプロイ出来るようになります。
なお、今回のアップデートにあわせて次のドキュメントも追加されておりました。
今回はまずこちらのドキュメントに従って Python とカスタムランタイム(ドキュメントでは Go を使用)の関数をデプロイし、どのような関数が作成されるのか実際に試してみます。
なお、注意事項としてフルスタック Git ベースの環境では関数をバンドルするための Docker がサポートされていない旨が記載されています。
Amplify コンソール上での CI/CD 自動ビルドは使えずカスタムパイプライン(AWS CodePipeline、Amazon CodeCatalyst、GitHub Actions など)[1]を使ってデプロイする必要があるようです。
Python
まず、Python ですが、こちらはほぼドキュメントどおりで実装可能です。
こんな感じで実装しました。
import { execSync } from "node:child_process";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import { defineFunction } from "@aws-amplify/backend";
import { DockerImage, Duration } from "aws-cdk-lib";
import { Code, Function, Runtime } from "aws-cdk-lib/aws-lambda";
const functionDir = path.dirname(fileURLToPath(import.meta.url));
export const hogeFunctionHandler = defineFunction(
(scope) =>
new Function(scope, "say-hello", {
handler: "index.handler",
runtime: Runtime.PYTHON_3_9, // or any other python version
timeout: Duration.seconds(20), // default is 3 seconds
code: Code.fromAsset(functionDir, {
bundling: {
image: DockerImage.fromRegistry("dummy"),
local: {
tryBundle(outputDir: string) {
execSync(
`python3 -m pip install -r ${path.join(functionDir, "requirements.txt")} -t ${path.join(outputDir)} --platform manylinux2014_x86_64 --only-binary=:all:`
);
execSync(`rsync -rLv ${functionDir}/* ${path.join(outputDir)}`);
return true;
},
},
},
}),
})
);
import json
def handler(event, context):
return {
"statusCode": 200,
"body": json.dumps({
"message": "Hello World",
}),
}
ただし、バンドルオプション内で requirements.txt を参照しており次のエラーが出る場合があるので、依存ライブラリがない場合でも空の requirements.txt は配置しておきましょう。
Failed to instantiate custom function provider
Caused By: Failed to bundle asset amplify-hoge0109app-iwasatakahito-sandbox-8636202ca4/function/say-hello/Code/Stage, bundle output is located at /Users/iwasa.takahito/work/hoge0109amplify/hoge0109app/.amplify/artifacts/cdk.out/asset.0190349d29e087b536bb3276a205a7a839b0f2c8da2e233859f544ee7e12a7dd-error: Error: Command failed: python3 -m pip install -r /Users/iwasa.takahito/work/hoge0109amplify/hoge0109app/amplify/functions/hoge/requirements.txt -t /Users/iwasa.takahito/work/hoge0109amplify/hoge0109app/.amplify/artifacts/cdk.out/asset.0190349d29e087b536bb3276a205a7a839b0f2c8da2e233859f544ee7e12a7dd --platform manylinux2014_x86_64 --only-binary=:all:
ERROR: Could not open requirements file: [Errno 2] No such file or directory: '/Users/iwasa.takahito/work/hoge0109amplify/hoge0109app/amplify/functions/hoge/requirements.txt'
カスタムランタイム(Go)
Go は resource.ts の実装がドキュメントどおりだとエラーになります。何箇所かおかしいところがありました。
なので、次のように直しています。
import { execSync } from "node:child_process";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import { defineFunction } from "@aws-amplify/backend";
import { DockerImage, Duration } from "aws-cdk-lib";
import { Code, Function, Runtime } from "aws-cdk-lib/aws-lambda";
const functionDir = path.dirname(fileURLToPath(import.meta.url));
export const fugaFunctionHandler = defineFunction(
(scope) =>
new Function(scope, "fuga", {
handler: "bootstrap",
runtime: Runtime.PROVIDED_AL2023,
timeout: Duration.seconds(3), // default is 3 seconds
code: Code.fromAsset(functionDir, {
bundling: {
image: DockerImage.fromRegistry("dummy"),
local: {
tryBundle(outputDir: string) {
execSync(`rsync -rLv ${functionDir}/* ${path.join(outputDir)}`);
execSync(
`cd ${path.join(outputDir)} && GOARCH=amd64 GOOS=linux go build -tags lambda.norpc -o ${path.join(outputDir)}/bootstrap ${functionDir}/main.go`
);
return true;
},
},
},
}),
}),
);
main.go
はそのままでいきました。
package main
import (
"context"
"fmt"
"github.com/aws/aws-lambda-go/lambda"
)
type Event struct {
Arguments Arguments `json:"arguments"`
}
type Arguments struct {
Title string `json:"phone"`
Msg string `json:"msg"`
}
func HandleRequest(ctx context.Context, event Event) (string, error) {
fmt.Println("Received event: ", event)
// fmt.Println("Message sent to: ", event.Arguments.Msg)
// You can use lambda arguments in your code
return "Hello World!", nil
}
func main() {
lambda.Start(HandleRequest)
}
なお Go のビルドが必要なので、ドキュメント記載のとおりローカルへのインストールと事前にビルドコマンドの実行が必要です。
go mod init lambda
go mod tidy
関数のデプロイと Lambda コンソール上での確認
上記プロセスを経て Functions モジュールが準備出来たら、あとは Node.js と同じです。
defineBackend に取り込んでやれば関数スタックに含まれてデプロイされます。
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { data } from './data/resource';
import { hogeFunctionHandler } from './functions/hoge/resource'
import { fugaFunctionHandler } from './functions/fuga/resource'
/**
* @see https://docs.amplify.aws/react/build-a-backend/ to add storage, functions, and more
*/
defineBackend({
auth,
data,
hogeFunctionHandler,
fugaFunctionHandler,
});
今回はサンドボックスへデプロイし、マネジメントコンソールから関数が作成されたのか確認しました。
まずは Python です。
良いですね。作成されています。
続いてこちらはカスタムランタイム(Go)です。
こちらも良さそうです。
さいごに
本日は AWS Amplify Gen2 の defineFunction が Node.js 以外もサポートしたので試してみました。
これはなかなかうれしい方が多いのでは。
マネージドランタイムであれば .NET とか Java、カスタムランタイムであれば Rust とか PHP もいけるってことですよね。
欲をいうと、Node.js よりもちょっと面倒な実装が多いので SAM みたいにランタイムごとにうまくビルドして欲しいのと、カスタムパイプラインに実装が必要になってしまうので Docker をサポートしてもらえると、すごく嬉しいです。
このあたりは今後のアップデートに期待したいです。